package thread;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: wx
 * Date: 2024-01-13
 * Time: 16:40
 */
//考虑阻塞(阻塞规则)  队列满了就要阻塞
//这个队列,一定是"要么空,要么满" 不能即是空,有又是满

class MyBlockingQueue {
    private String[] elems = null;
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    //引入锁对象,如果使用this也可以
    private Object locker = new Object();

    public MyBlockingQueue(int capcity) {
        elems = new String[capcity];
    }

    public void put(String elem) throws InterruptedException {
        //锁加到这里和加到方法上本质上是一样的.加到方法上是给 this 加锁,加到这里是给 locker 加锁
        synchronized (locker) {
            while (size >= elems.length) {
                //队列满了
                //需要阻塞,把 wait 加入到 if 中,恰好这个 if 刚好在 synchronized 里边
                //不仅需要 wait 阻塞,也需要其他线程把它给唤醒.
                //队列不满就可以唤醒,什么是队列不满?
                //出队列成功,就是队列不满. 我们可以在出队列(queue.take();)里边把它唤醒
                locker.wait();
            }
            // put 里边都是写操作,所以这几个代码必须用锁包裹起来
            //新的元素要放到 tail 指向的位置上
            elems[tail] = elem;
            tail++;
            if (tail >= elems.length) {
                tail = 0;
            }
            size++;

            //入队列成功之后进行唤醒
            locker.notify();

        }

          /*
            if (size >= elems.length) {  if 和 while 的区别
                                         if属于是一锤子买卖 之判定一次条件 但是一旦程序进入阻塞之后,再被唤醒中间隔得时间就是 沧海桑田了
                                         这个过程中会有很多的变数,有了很多的变数,就无法保证入队列的条件是否具备
                                         改成while 意味着wait唤醒之后,在判定一次条件
                                         wait之前判定一次,唤醒之后在判定一次(相当于多做了一次判定)
                                         如果再次确认,发现队列还是满的,那就继续等待.
                                         每次被唤醒都会在确认一下是否继续执行
                //队列满了
                //需要阻塞,把 wait 加入到 if 中,恰好这个 if 刚好在 synchronized 里边
                //不仅需要 wait 阻塞,也需要其他线程把它给唤醒.
                //队列不满就可以唤醒,什么是队列不满?
                //出队列成功,就是队列不满. 我们可以在出队列(queue.take();)里边把它唤醒

                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            */


   /*
    public void put(String elem) {
        //锁加到这里和加到方法上本质上是一样的.加到方法上是给 this 加锁,加到这里是给 locker 加锁
        synchronized (locker) {
            if (size >= elems.length) {
            需要将这里修改成阻塞 怎么修改呢?
            也很简单,使用 wait 来进行阻塞
                //队列满了
                //后续需要让这个代码能够阻塞
                return;
            }
            // put 里边都是写操作,所以这几个代码必须用锁包裹起来
            //新的元素要放到 tail 指向的位置上
            elems[tail] = elem;
            tail++;
            if (tail >= elems.length) {
                tail = 0;
            }
            size++;
        }
    }
    */
    }


    public String take() throws InterruptedException {
        String elem = null;
        synchronized (locker) {
            while (size == 0) {
                //队列空了
                //需要代码阻塞
                locker.wait();
            }
            //取出head并返回
            elem = elems[head];
            head++;
            if (head >= elems.length) {
                head = 0;
            }
            //这个代码不能遗漏
            size--;
            //元素出队列成功之后,加上唤醒
            locker.notify();
            //队列满了需要出队列,同样,队列空了,再出队列,同样也需要阻塞,同样是在另一个入队列成功后的线程中唤醒
        }
        return elem;
    }
}

//引入线程安全问题,是为了多线程安全
// 需要给 put 和 take 加上锁.

/*
class MyBlockingQueue {
    private String[] elems = null;
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    //引入锁对象,如果使用this也可以
    private Object locker = new Object();

    public MyBlockingQueue(int capcity) {
        elems = new String[capcity];
    }

    */
/*
public void put(String elem) {
    if (size >= elems.length) {
        //队列满了
        //后续需要让这个代码能够阻塞
        return;
    }
    如果这里不加锁会出现少一次判定 而导致size多加
    而且会覆盖之前的元素
    必然是有问题的
    // put 里边都是写操作,所以这几个代码必须用锁包裹起来
    synchronized (locker) {
        //新的元素要放到 tail 指向的位置上
        elems[tail] = elem;
        tail++;
        if (tail >= elems.length) {
            tail = 0;
        }
        size++;
    }
}
*//*

    public void put(String elem) {
        //锁加到这里和加到方法上本质上是一样的.加到方法上是给 this 加锁,加到这里是给 locker 加锁
        synchronized (locker) {
        if (size >= elems.length) {
            //队列满了
            //后续需要让这个代码能够阻塞
            return;
        }
        // put 里边都是写操作,所以这几个代码必须用锁包裹起来
            //新的元素要放到 tail 指向的位置上
            elems[tail] = elem;
            tail++;
            if (tail >= elems.length) {
                tail = 0;
            }
            size++;
        }
    }

    public String take() {
        String elem = null;
        synchronized (locker){
            if (size == 0) {
                //队列空了
                //后续需要这个代码阻塞
                return null;
            }
            //取出head并返回
             elem = elems[head];
            head++;
            if(head >= elems.length){
                head = 0;
            }
            //这个代码不能遗漏
            size--;
            return elem;
        }
    }
}
*/


//为了简单,不写作泛型的形式,考虑存储的元素就是单纯的 String
/*
class MyBlockingQueue {
    private String[] elems = null;
    private int head = 0;
    private int tail = 0;
    private int size = 0;
    public MyBlockingQueue(int capcity) {
        elems = new String[capcity];
    }

    public void put(String elem) {
        if(size >= elems.length){
            //队列满了
            //后续需要让这个代码能够阻塞
            return;
        }
        //新的元素要放到 tail 指向的位置上
        elems[tail] = elem;
        tail++;
        if(tail >= elems.length){
            tail = 0;
        }
        size++;
    }

    public String take() {
        if (size == 0) {
            //队列空了
            //后续需要这个代码阻塞
            return null;
        }
        //取出head并返回
        String elem = elems[head];
        head++;
        if(head >= elems.length){
            head = 0;
        }
        //这个代码不能遗漏
        size--;
        return elem;
    }
}
*/

    public class ThreadDemo28 {
        // 我们要尝试实现一个阻塞队列,达到和标准库的队列类似的效果
        //如何实现一个阻塞队列?
        //1)先实现普通队列     基于数组来实现(环形队列)
        //2)再加上线程安全
        //3)再加上阻塞功能
        public static void main(String[] args) {
            MyBlockingQueue queue = new MyBlockingQueue(1000);
       /*
        queue.put("aaaa");
        queue.put("bbbb");
        queue.put("cccc");
        queue.put("dddd");

        String elem = "";
        elem = queue.take();
        System.out.println("elem = " + elem);
        elem = queue.take();
        System.out.println("elem = " + elem);
        elem = queue.take();
        System.out.println("elem = " + elem);
        elem = queue.take();
        System.out.println("elem = " + elem);
        */

            //生产者
            Thread t1 = new Thread(() -> {
                int n = 1;
                while (true) {
                    try {
                        queue.put(n + "");
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("生产元素 " + n);
                    n++;
                    /*try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }*/
                }
            });

            //消费者
            Thread t2 = new Thread(() -> {
                while (true) {
                    try {
                        String n = queue.take();
                        System.out.println("消费者元素 " + n);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            t1.start();
            t2.start();
            //实际开发中,生产者消费者模型,往往是多个生产者多个消费者
            //这里的生产者和消费者往往不仅仅是一个线程,也可能是一个独立的服务器程序,甚至是一组服务器程序
            // 最核心的部分任然是阻塞队列
            // 使用synchronized 和 wait/notify 达到线程安全 & 阻塞.
        }
    }


